#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "open.h"

#define MAXLINE     1024

typedef struct {            /* represents a pool of connected descriptiors */
    int maxfd;              /* largest descriptior in read_set */
    fd_set read_set;        /* set of all active descriptors */
    fd_set ready_set;       /* subset of descriptors ready for reading */
    int nready;             /* number of ready descriptors from select */
    int maxi;               /* highwater index into client array */
    int clientfd[FD_SETSIZE];       /* set of active descriptors */
} pool;

int byte_cnt = 0;  /* counts total bytes received by server */

void init_pool(int listenfd, pool *p);
void add_client(int connfd, pool *p);
void check_clients(pool *p);

int main(int argc, char const* argv[])
{
    int listenfd, connfd, port, clientlen = sizeof(struct sockaddr_in);
    struct sockaddr_in clientaddr;
    static pool pool;

    printf("FD_SETSIZE is %d\n", FD_SETSIZE);

    if (argc != 2)
    {
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        exit(0);
    }
    port = atoi(argv[1]);

    listenfd = open_listenfd(port);
    init_pool(listenfd, &pool);
    while(1)
    {
        /* wait for listening/conntected descriptors to become ready */
        pool.ready_set = pool.read_set;
        pool.nready = select(pool.maxfd+1, &pool.ready_set, NULL, NULL, NULL);
        
        /* if listening descriptor ready, add new client to pool */
        if (FD_ISSET(listenfd, &pool.ready_set))
        {
            connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen);
            if (connfd < 0)
            {
                perror("accept error");
                exit(0);
            }

            add_client(connfd, &pool);
        }

        /* echo a text line from each ready connected descriptor */
        check_clients(&pool);
    }

    return 0;
}

void init_pool(int listenfd, pool *p)
{
    /* initially, there are no connected descriptors */
    int i;
    p->maxi = -1;
    for (i = 0; i < FD_SETSIZE; i++) {
        p->clientfd[i] = -1;
    }

    /* initially, listenfd is only member of select read set */
    p->maxfd = listenfd;
    FD_ZERO(&p->read_set);
    FD_SET(listenfd, &p->read_set);
}   

 void add_client(int connfd, pool *p)
{
    int i;

    p->nready --;
    for (i = 0; i < FD_SETSIZE; i++) {  /* find an available slot */
        if (p->clientfd[i] < 0)
        {
            /* add connected descriptor to the pool */
            p->clientfd[i] = connfd;
            
            /* add the descritor to descriptor set */
            FD_SET(connfd, &p->read_set);

            /* update max descriptor and pool highwater mark */
            if (connfd > p->maxfd)
                p->maxfd = connfd;
            if (i > p->maxi)
                p->maxi = i;
            break;
        }
    }

    if (i == FD_SETSIZE)    /* couldn't find an empty slot */
    {
        fprintf(stderr, "add_client error: Too many clients");
        exit(0);
    }
}

void check_clients(pool *p)
{
    int i, connfd, n;
    char buf[MAXLINE];
    
    for (i = 0; i <= p->maxi; i++) {
        connfd = p->clientfd[i];
        
        /* if the descriptor is ready, echo a text line from it */
        if ((connfd > 0) && (FD_ISSET(connfd, &p->ready_set)))
        {
            p->nready --;
            if ((n = read(connfd, buf, MAXLINE)) != 0)
            {
                byte_cnt += n;
                printf("server received %d(%d total) bytes on the fd %d\n", 
                    n, byte_cnt, connfd);
                write(connfd, buf, n);
            }
            /* EOF detected, remove descriptor from pool */
            else
            {
                close(connfd);
                FD_CLR(connfd, &p->read_set);
                p->clientfd[i] = -1;
            }
        }
    }
}
